﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using antlr;

using Eco.ObjectRepresentation;
using Eco.Services;

using CommonClasses.Model;
using CommonClasses.Logger;
using CommonClasses.Services;
using CommonClasses.Utility;
using Descriptions_DomainModel.Model;
using LPMEDTranslator.Model;
using VMLua;
using EcoExtensions.Model;
using System.Windows.Forms;


namespace FunctionManager
{
    /// <summary>
    /// Класс, реализующий JIT-компиляцию библиотечных ф-ий и регистрацию объектов в LuaTable
    /// </summary>
    public class FunctionManager : IFunctionManager
    {
        #region Private fields

        /// <summary>
        /// Логгер
        /// </summary>
        private ILogger logger = null;

        /// <summary>
        /// Провайдер сервисов ECO
        /// </summary>
        private IEcoServiceProvider serviceProvider = null;

        /// <summary>
        /// Транслятор, который используется для компиляции функций
        /// </summary>
        private ITranslatorService Translator
        {
            get
            {
                return serviceProvider.GetEcoService<ITranslatorService>();
            }
        }

        /// <summary>
        /// Lua-машина
        /// </summary>
        private IVirtualMachineService VirtualMachine
        {
            get
            {
                return serviceProvider.GetEcoService<IVirtualMachineService>();
            }
        }

        /// <summary>
        /// Интерпретатор Ocl из текщуего IEcoServiceProvider
        /// </summary>
        private IOclService Ocl
        {
            get
            {
                return serviceProvider.GetEcoService<IOclService>();
            }
        }

        /// <summary>
        /// Список имён ф-ий, которые выполняются в данный момент
        /// </summary>
        private List<string> launchedFunctions = null;

        /// <summary>
        /// Описание оборудования
        /// </summary>
        private DomainModel domainModel = null;

        /// <summary>
        /// Список ф-ий, объявленных динамически (в тексте программных модулей)
        /// Для каждой ф-ии хранится пара вида (Функция * Результат компиляции ф-ии)
        /// </summary>
        private Dictionary<Function, CompilationData> dynamicDefinedFunctions = new Dictionary<Function, CompilationData>();

        /// <summary>
        /// Список имён в таблице луа, которые нужно занулить после окончания выполнения
        /// </summary>
        private List<string> variablesToNull = null;

        #endregion

        /// <summary>
        /// Коснструктор JIT-компилятора библиотечных ф-ий и регистрацию объектов в LuaTable
        /// </summary>
        /// <param name="serviceProvider">провейдер сервисов ECO</param>
        /// <param name="logger">логгер</param>
        public FunctionManager(IEcoServiceProvider serviceProvider, ILogger logger)
        {
            if (logger == null)
                throw new NullReferenceException("Logger cannot be null!");

            this.logger = logger;

            if (serviceProvider == null)
                throw new NullReferenceException("ServiceProvider cannot be null!");

            this.serviceProvider = serviceProvider;

            if (Translator == null)
                throw new NullReferenceException("ITranslatorService must be registered in EcoSpace before FunctionManager!");

            if (VirtualMachine == null)
                throw new NullReferenceException("IVirtualMachineService must be registered in EcoSpace before FunctionManager!");

            launchedFunctions = new List<string>();
            variablesToNull = new List<string>();

            InitializeVM();
        }

        #region Public methods

        public void DeleteFunctionInstance(string name)
        {
            VirtualMachine.RegisterObject(GetStateVariableName(name), 0);
        }

        #endregion


        #region IFunctionManager Members

   
        public void RegisterFunction(string functionName, string parameters)
        {
            //ищем ф-ю
            var function = Ocl.Evaluate(
                @"DomainModel.allInstances->first.FunctionLibrary.Functions->select(func|func.Name = '" + functionName + "')->first").AsObject as Function;

            if (function == null)
            {
                logger.WriteMessage(MessagePriority.Error,
                    "Trying to execute function '" + functionName + "' that doesn't exists");
                throw new Exception("Trying to execute function '" + functionName + "' that doesn't exists");
                return;
            }

            #region Translatin'

            var compdata = new CompilationData(serviceProvider);

            if (!dynamicDefinedFunctions.ContainsKey(function))
            {
                #region если ф-ия статически объявлена в библиотеке

                var sourceCode = "";
                //код необходимо дополнить объявлением параметров
                if (function.Parameters.Count > 0)
                {
                    sourceCode = "VAR ";
                    for (int i = 0; i < function.Parameters.Count - 1; i++)
                        sourceCode += function.Parameters[i].Name + ", ";

                    sourceCode += function.Parameters[function.Parameters.Count - 1].Name
                        + " : int;\n";

                    var parametersAsArray = parameters.Split(';');

                    for (int i = 0; i < function.Parameters.Count; i++)
                        sourceCode += function.Parameters[i].Name + " = " + parametersAsArray[i] + ";\n";
                }

                sourceCode += function.Code;

                object tree = null;
                var report = Translator.SyntaxCheck(sourceCode,
                    new iLPMEDFirstStageGrammar(serviceProvider),
                    new List<IASTTransformer> { 
                        new iLPMEDTocLPMEDTransformer(serviceProvider, functionName)},
                    out tree);
                compdata.Tree = new EcoObject(serviceProvider, tree);

                if (report.Errors.Count > 0)
                {
                    logger.WriteMessage(MessagePriority.Critical,
                        report.Errors.Aggregate("Function to be executed contains compilation errors:",
                            (acc, error) => acc + "\n" + error.ToString()));
                    Console.WriteLine(report.Errors.Aggregate("Function to be executed contains compilation errors:",
                            (acc, error) => acc + "\n" + error.ToString()));
                    throw new Exception(report.Errors.Aggregate("Function to be executed contains compilation errors:",
                            (acc, error) => acc + "\n" + error.ToString()));
                    return;
                }

                //т.к. функции являются модулями интерпретации, 
                //то на семантику мы их не проверяем, а надо бы.
                //Для семантической проверки нужно указывать контекст
                //например, внешние для данной ф-ии переменные

                //report = Translator.SemanticCheck(tree);

                //if (report.Errors.Count > 0)
                //{
                //    logger.WriteMessage(MessagePriority.Critical,
                //        report.Errors.Aggregate("Function to be executed contains compilation errors:",
                //            (acc, error) => acc + "\n" + error.ToString()));
                //    return;
                //}

                Translator.Translate(ref compdata);

                #endregion
            }
            else
                compdata = dynamicDefinedFunctions[function]; //ф-я компилируется при объявлении

            if (compdata.CompiledCode == string.Empty)
            {
                logger.WriteMessage(MessagePriority.Error,
                    "There are errors occured during compiling function '" + functionName + "'. It won't be executed.");
                throw new Exception("There are errors occured during compiling function '" + functionName + "'. It won't be executed.");
                return;
            }

            #endregion

            VirtualMachine.RegisterObject(GetStateVariableName(functionName), 0);

            launchedFunctions.Add(functionName);

            SendFunctionToLua(functionName, compdata);
        }

        public List<Exception> DefineFunction(Antlr.Runtime.IToken name, string body)
        {
            if (domainModel == null)
                return new List<Exception>();

            #region проверка на существование

            var alreadyDefined = domainModel.FunctionLibrary.Functions
                    .FirstOrDefault((fun) => fun.Name == name.Text);

            if (alreadyDefined != null)
                return new List<Exception> 
                { 
                    new RecognitionException("Фукнция с таким именем уже объявлена!")
                    {
                        column = name.CharPositionInLine,
                        line = name.Line
                    }
                };

            #endregion

            var report = new List<Exception>();

            #region функция добавления ошибок

            Action<Error> collectToReport = (error) =>
                        {
                            report.Add(new RecognitionException(error.Message)
                                {
                                    column = error.Point.Point.X,
                                    line = error.Point.Point.Y
                                });
                        };

            #endregion

            object tree = null;

            #region синтаксис

            Translator.SyntaxCheck(body,
                new iLPMEDFirstStageGrammar(serviceProvider),
                new List<IASTTransformer> {
                    new iLPMEDTocLPMEDTransformer(serviceProvider, name.Text)},
                out tree)
                .Errors.ForEach(collectToReport);

            if (report.Count > 0)
                return report;

            #endregion

            //семантику мы вынуждены не проверять 
            //- завязку на контекст нам не организовать пока...
            /*#region семантика

            Translator.SemanticCheck(tree)
                .Errors.ForEach(collectToReport);

            if (report.Count > 0)
                return report;

            #endregion*/



            #region Создание функции

            //динамически объявляемые ф-ии компилируются один раз
            var compData = new CompilationData(serviceProvider);
            compData.Tree = new EcoObject(serviceProvider, tree);
            Translator.Translate(ref compData);

            var funcToAdd = new Function(serviceProvider)
                {
                    Code = body,
                    Name = name.Text
                };
            domainModel.FunctionLibrary.Functions.Add(funcToAdd);
            dynamicDefinedFunctions.Add(funcToAdd, compData);

            #endregion

            return report;
        }

        public void InitializeDM(DomainModel domainModel)
        {
            this.domainModel = domainModel;
        }

        public void ClearLibrary()
        {
            foreach(var dynFunc in dynamicDefinedFunctions.Keys)
                domainModel.FunctionLibrary.Functions.Remove(dynFunc);
            dynamicDefinedFunctions.Clear();
        }

        #endregion


        #region Private methods

        /// <summary>
        /// имя переменной, отражающей состояние выполнения ф-ии
        /// 0 - выполняется
        /// 1 - выполнено
        /// </summary>
        private string GetStateVariableName(string functionName)
        {
            return "FunctionsTable." + functionName + "_State";
        }

        private void SendFunctionToLua(string functionName, CompilationData compdata)
        {
            var actor = new ActiveObject(functionName);
            actor.LuaProgram = compdata.CompiledCode;
            actor.ScriptOneStepCode = compdata.CoroutineCallString;

            SubscribeOnActor(actor);

            VirtualMachine.RegisterActiveObject(actor);
        }

        private void SubscribeOnActor(ActiveObject actor)
        {
            actor.OnDone += new EventHandler(Actor_OnDone);
            actor.OnRunTimeErrorOccured += new RunTimeErrorHandler(HandleError);
        }

        private void KillSubscribeOnActor(ActiveObject actor)
        {
            actor.OnDone -= new EventHandler(Actor_OnDone);
            actor.OnRunTimeErrorOccured -= new RunTimeErrorHandler(HandleError);
        }

        /// <summary>
        /// Инициализация
        /// </summary>
        private void InitializeVM()
        {
            //в этой таблице будут находиться переменные - состояния выполняемых функций
            VirtualMachine.RegisterTable("FunctionsTable");
            //регистрируем себя для доступа при вызове ф-ии
            VirtualMachine.RegisterObject("FunctionManager", this);
            //нужно подписаться на окончание выполнения - чтобы можно было нулить переменные в луа
            VirtualMachine.OnAllCoroutineDead += new MethodInvoker(OnAllCoroutineDead);
        }

        #endregion

        #region Event handlers

        private void HandleError(object actor, object[] messages)
        {
            var activeobj = actor as ActiveObject;

            var mess = messages.Aggregate(activeobj.Name + "\n" + activeobj.LuaProgram + "\nDoString returned: ",
                (acc, obj) => acc += "\n" + obj.ToString());

            mess = mess.Replace(">", " &gt; ");
            mess = mess.Replace("<", " &lt; ");

            logger.WriteMessage(MessagePriority.Error,
                    "FunctionManager: Runtime error in function: " + mess);
        }

        private void Actor_OnDone(object sender, EventArgs e)
        {
            var activeobj = sender as ActiveObject;
            if (launchedFunctions.Contains(activeobj.Name))
                VirtualMachine.RegisterObject(GetStateVariableName(activeobj.Name), 1);
            KillSubscribeOnActor(activeobj);

            variablesToNull.Add(GetStateVariableName(activeobj.Name));
        }

        private void OnAllCoroutineDead()
        {
            foreach (var variable in variablesToNull)
                VirtualMachine.RegisterObject(variable, 0);

            variablesToNull.Clear();
        }

        #endregion
    }
}
